package com.cloud.xtools.gateway.filter;

import com.cloud.xtools.gateway.redis.RedisService;
import com.cloud.xtools.gateway.util.JwtUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * @author Daixinguo
 */
@Component
@RequiredArgsConstructor
public class AuthenticationFilter implements GlobalFilter, Ordered {

    private final JwtUtil jwtUtil;

    private final RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        // 注意这里的 request 对象不再是 HttpServletRequest 类型的对象啦，而是 org.springframework.http.server.reactive.ServerHttpRequest 类型的对象
        // 这是因为我们在配置文件中修改了启动类型为 reactive（响应式编程模型）
        // spring.main.web-application-type=reactive
        // 1、响应式编程模型：Spring Boot 2.0 引入了响应式编程模型，支持非阻塞和异步处理。网关服务通常需要处理大量的并发请求，使用响应式编程模型可以提高系统的吞吐量和性能。
        // 2、非阻塞 I/O：响应式编程模型基于非阻塞 I/O，这意味着在处理请求时不会阻塞线程，从而可以更高效地利用系统资源。这对于高并发场景尤为重要。
        // 3、更好的资源利用率：传统的阻塞 I/O 模型在处理大量并发请求时，需要为每个请求分配一个线程，这会导致线程资源的浪费。而响应式编程模型可以使用少量的线程处理大量的请求，从而更好地利用系统资源。
        // 4、与 Spring WebFlux 集成：Spring WebFlux 是 Spring Framework 5.0 引入的响应式 Web 框架，它提供了完整的响应式编程支持。配置 spring.main.web-application-type=reactive 可以确保应用启动时使用 Spring WebFlux 而不是传统的 Spring MVC。
        // 5、兼容性和扩展性：响应式编程模型不仅适用于网关服务，还可以与其他响应式组件和服务无缝集成，提供更好的兼容性和扩展性。

        // HttpServletRequest 类型的 request 和 ServerHttpRequest 类型的 request 提供的接口不一样。
        // 而且 ServerHttpRequest 没有 attribute 属性，所以无法设置自定义属性，用户信息只能通过请求头设置。

        String token = null;
        List<String> headers = request.getHeaders().get("Authorization");
        if (headers != null && !headers.isEmpty()) {
            token = headers.getFirst();
        }
        if (token != null && !token.isEmpty()) {

            // System.out.println("token = " + token);

            // 解析 token 如果不存在 token 则交由后续的拦截器处理，在控制器中将通过 @DisAuth 注解跳过权限检查
            if (!jwtUtil.isTokenExpired(token)) {
                // 获取 jwt id
                String jwtId = jwtUtil.extractId(token);
                // System.out.println(jwtId);
                if (redisService.getString(jwtId) != null){

                    String username = jwtUtil.extractUsername(token);
                    String userId = jwtUtil.getUserId(token).toString();

                    // System.out.println("userId = " + userId);

                    // 查询用户的权限，并将权限信息设置到请求头中
                    // 两种方案：
                    // 1、和 user 服务共用一个 redis，这里直接从 redis 中获取用户权限信息
                    // 2、和 user 服务解耦，通过 user 服务提供的接口远程调用获取用户权限信息
                    // 由于我们还涉及到登录用户的白名单，这里就使用方案 1（与 user 服务共用一个 redis）
                    // 获取当前用户拥有的权限
                    String roleKey = "user_role:" + userId;
                    Set<Object> roleIds = redisService.getSet(roleKey);
                    Set<Object> allPermissions = new HashSet<>();
                    roleIds.forEach(roleId -> {
                        String permissionKey = "role_permission:" + roleId;
                        allPermissions.addAll(redisService.getSet(permissionKey));
                    });
                    // Dump.json(allPermissions);
                    StringBuilder permissions = new StringBuilder();
                    for (var permission : allPermissions) {
                        if(permissions.isEmpty()){
                            permissions.append(permission);
                        } else {
                            permissions.append(",").append(permission);
                        }
                    }

                    // System.out.println("permissions = " + permissions);

                    // 将用户信息设置到请求头中
                    exchange = exchange.mutate()
                            .request(builder -> {
                                builder.header("username", username);
                                builder.header("user-id", userId);
                                builder.header("permissions", permissions.toString());
                            })
                            .build();
                }
            }
        }

        // 放行
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序，值越小，优先级越高
        return 0;
    }
}
